/**
 * @module palette
 * @description
 * 这个模块包含了所有与 `TerrainSchematic` 调色板相关的工具函数。
 * 包括优化调色板、获取或创建条目、以及各种方块替换操作。
 */

import type TerrainSchematic from "../schematic";
import { ReplacementMap } from "../types";

/**
 * 清理并优化调色板。
 * 移除所有未在 `schematicBlocks` 中被引用的调色板条目，并重新索引。
 */
export function optimizePalette(schematic: TerrainSchematic): void {
  if (schematic.schematicBlocks.length === 0) {
    schematic.data.palette = [];
    return;
  }

  const usedPaletteIndices = new Set<number>();
  for (const block of schematic.schematicBlocks) {
    usedPaletteIndices.add(block.p);
  }

  const oldPalette: { id: voxelId; rot?: voxelRotation }[] = [];
  for (let i = 0; i < schematic.data.palette.length; i += 2) {
    oldPalette.push({
      id: schematic.data.palette[i] as voxelId,
      rot: schematic.data.palette[i + 1] as voxelRotation,
    });
  }

  const newPalette: { id: voxelId; rot?: voxelRotation }[] = [];
  const oldToNewIndexMap = new Map<number, number>();

  for (let i = 0; i < oldPalette.length; i++) {
    if (usedPaletteIndices.has(i)) {
      oldToNewIndexMap.set(i, newPalette.length);
      newPalette.push(oldPalette[i]);
    }
  }

  for (const block of schematic.schematicBlocks) {
    block.p = oldToNewIndexMap.get(block.p)!;
  }

  schematic.data.palette = newPalette.flatMap((p) => [p.id, p.rot ?? 0]);
}

/**
 * 获取或创建一个方块类型在调色板中的索引。
 * 如果该方块类型已存在，则返回其索引；否则，将其添加到调色板并返回新索引。
 * @param voxelId 方块ID
 * @param voxelRot 方块旋转
 * @returns 调色板中的索引
 */
export function getOrCreatePaletteIndex(
  schematic: TerrainSchematic,
  voxelId: voxelId,
  voxelRot: voxelRotation
): number {
  const key = `${voxelId}:${voxelRot}`;

  // 重建一个临时的Map来快速查找
  const tempPaletteMap = new Map<string, number>();
  for (let i = 0; i < schematic.data.palette.length; i += 2) {
    const id = schematic.data.palette[i];
    const rot = schematic.data.palette[i + 1];
    tempPaletteMap.set(`${id}:${rot}`, i / 2);
  }

  if (tempPaletteMap.has(key)) {
    return tempPaletteMap.get(key)!;
  } else {
    const newIndex = schematic.data.palette.length / 2;
    schematic.data.palette.push(voxelId, voxelRot);
    return newIndex;
  }
}

/**
 * 获取一个完整的、非扁平化的调色板数组。
 * @returns 一个包含 {id, rot} 对象的数组。
 */
export function getPalette(
  schematic: TerrainSchematic
): { id: voxelId; rot: voxelRotation }[] {
  const palette: { id: voxelId; rot: voxelRotation }[] = [];
  for (let i = 0; i < schematic.data.palette.length; i += 2) {
    palette.push({
      id: schematic.data.palette[i] as voxelId,
      rot: schematic.data.palette[i + 1] as voxelRotation,
    });
  }
  return palette;
}

/**
 * 根据提供的规则替换当前蓝图中的方块类型。
 * @param replacementMap 一个 `Map` 对象，定义了如何将旧方块ID替换为新方块。
 * @returns `this`，允许链式调用。
 */
export function replaceBlocks(
  schematic: TerrainSchematic,
  replacementMap: ReplacementMap
): TerrainSchematic {
  // 1. 重建原始调色板
  const oldPalette: { id: voxelId; rot: voxelRotation }[] = [];
  for (let i = 0; i < schematic.data.palette.length; i += 2) {
    oldPalette.push({
      id: schematic.data.palette[i] as voxelId,
      rot: schematic.data.palette[i + 1] as voxelRotation,
    });
  }

  // 2. 应用替换规则
  const replacedPalette = oldPalette.map((block) => {
    const replacement = replacementMap.get(block.id);
    if (replacement) {
      return {
        id: replacement.id,
        rot: replacement.rot !== undefined ? replacement.rot : block.rot,
      };
    }
    return block;
  });

  // 3. 重新构建一个优化的新调色板，并创建旧索引到新索引的映射
  const newPaletteOptimized: { id: voxelId; rot: voxelRotation }[] = [];
  const newPaletteMap = new Map<string, number>();
  const oldToNewIndexMap = new Map<number, number>();

  replacedPalette.forEach((block, oldIndex) => {
    const key = `${block.id}:${block.rot}`;
    if (!newPaletteMap.has(key)) {
      newPaletteMap.set(key, newPaletteOptimized.length);
      newPaletteOptimized.push(block);
    }
    oldToNewIndexMap.set(oldIndex, newPaletteMap.get(key)!);
  });

  // 4. 使用新的调色板索引更新方块列表
  for (const block of schematic.schematicBlocks) {
    const newPaletteIndex = oldToNewIndexMap.get(block.p);
    if (newPaletteIndex !== undefined) {
      block.p = newPaletteIndex;
    }
  }

  schematic.data.palette = newPaletteOptimized.flatMap((p) => [
    p.id,
    p.rot ?? 0,
  ]);
  return schematic;
}

/**
 * 将蓝图内所有非空气方块替换为指定的方块类型。
 *
 * 此方法会遍历所有现存的方块（即非空气方块）并改变它们的类型。
 * 它不会在原本是空气的地方创建新的方块。
 *
 * @param voxelId 要替换成的方块ID。默认为0（空气），效果等同于清空所有非空气方块。
 * @param voxelRot 可选，替换方块的旋转方向，默认为0。
 * @param excludeIds 可选的方块ID数组。所有ID在此列表中的方块都将保持原样，不会被替换。
 * @returns `this`，允许链式调用。
 */
export function replaceAllBlocks(
  schematic: TerrainSchematic,
  voxelId: voxelId = 0,
  voxelRot: voxelRotation = 0,
  excludeIds: voxelId[] = []
): TerrainSchematic {
  // 快速路径: 如果没有排除项，使用旧的高效方法
  if (excludeIds.length === 0) {
    // 1. 只保留一个调色板项
    schematic.data.palette = [voxelId, voxelRot];
    // 2. 所有方块的p索引都指向0
    for (const block of schematic.schematicBlocks) {
      block.p = 0;
    }
    return schematic;
  }

  // 慢速路径: 存在排除项时，需要重建调色板
  // 此逻辑类似于 replaceBlocks
  // 1. 从扁平化数组中重建当前调色板
  const oldPalette: { id: voxelId; rot: voxelRotation }[] = [];
  for (let i = 0; i < schematic.data.palette.length; i += 2) {
    oldPalette.push({
      id: schematic.data.palette[i] as voxelId,
      rot: schematic.data.palette[i + 1] as voxelRotation,
    });
  }

  // 为排除的ID创建Set以便高效查找
  const exclusionSet = new Set(excludeIds);

  // 2. 创建一个"逻辑上替换后"的调色板。对于旧调色板中的每一项，
  //    决定是保留还是替换为填充方块
  const replacedPalette = oldPalette.map((block) => {
    // 如果方块ID在排除列表中，则保留原始方块
    if (exclusionSet.has(block.id)) {
      return block;
    }
    // 否则，将其替换为新的填充方块
    return { id: voxelId, rot: voxelRot };
  });

  // 3. 从"逻辑上替换后"的调色板中重建一个优化的新调色板，
  //    并创建一个从旧调色板索引到新调色板索引的映射。
  const newPaletteOptimized: { id: voxelId; rot: voxelRotation }[] = [];
  const newPaletteMap = new Map<string, number>();
  const oldToNewIndexMap = new Map<number, number>();

  replacedPalette.forEach((block, oldIndex) => {
    const key = `${block.id}:${block.rot}`;
    if (!newPaletteMap.has(key)) {
      newPaletteMap.set(key, newPaletteOptimized.length);
      newPaletteOptimized.push(block);
    }
    oldToNewIndexMap.set(oldIndex, newPaletteMap.get(key)!);
  });

  // 4. 更新 schematicBlocks 以使用新的调色板索引
  for (const block of schematic.schematicBlocks) {
    const newPaletteIndex = oldToNewIndexMap.get(block.p);
    if (newPaletteIndex !== undefined) {
      block.p = newPaletteIndex;
    }
  }

  // 5. 将主数据调色板更新为新的优化后的版本
  schematic.data.palette = newPaletteOptimized.flatMap((p) => [
    p.id,
    p.rot ?? 0,
  ]);

  return schematic;
}

/**
 * 根据一个带权重的模式来替换方块，可以用来创建更自然的纹理混合。
 * @param schematic 要操作的蓝图
 * @param patternMap 一个Map，键是要被替换的方块ID，值是一个包含新方块ID、旋转和权重的对象数组。
 * @returns `this`，允许链式调用。
 */
export function replaceByPattern(
  schematic: TerrainSchematic,
  patternMap: Map<
    voxelId,
    { id: voxelId; rot?: voxelRotation; weight: number }[]
  >
): TerrainSchematic {
  if (patternMap.size === 0) {
    return schematic;
  }

  // 1. 预处理模式，计算总权重并创建新的调色板索引
  const processedPatterns = new Map<
    voxelId,
    { totalWeight: number; choices: { p: number; weight: number }[] }
  >();
  for (const [sourceId, entries] of patternMap.entries()) {
    let totalWeight = 0;
    const choices: { p: number; weight: number }[] = [];
    for (const entry of entries) {
      totalWeight += entry.weight;
      choices.push({
        p: getOrCreatePaletteIndex(schematic, entry.id, entry.rot ?? 0),
        weight: entry.weight,
      });
    }
    processedPatterns.set(sourceId, { totalWeight, choices });
  }

  // 2. 遍历方块并应用替换
  const originalPalette = getPalette(schematic);
  for (const block of schematic.schematicBlocks) {
    const sourceBlockType = originalPalette[block.p];
    const pattern = processedPatterns.get(sourceBlockType.id);

    if (pattern) {
      let rand = Math.random() * pattern.totalWeight;
      for (const choice of pattern.choices) {
        rand -= choice.weight;
        if (rand <= 0) {
          block.p = choice.p;
          break;
        }
      }
    }
  }

  // 3. 清理调色板
  optimizePalette(schematic);
  return schematic;
}
